package engine;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

/**
 * Main game
 * @author Joshua Chang
 */
public class Game {
    // player card draw pile
    private List<PlayerCard> playerDeck;
    // infection card draw pile
    private List<City> infectionDeck;  
    // player card discard pile
    private List<PlayerCard> discardPlayerDeck;
    // infection card discard pile
    private List<City> discardInfectionDeck;
    
    // index of how many outbreaks have occurred
    private int outbreakIndex;
    // index of infection rate (NB: not infection rate itself!)
    private int infectionRateIndex;
    
    // array keeping track of which diseases have been cured
    private boolean[] cured;
    // array keeping track of which diseases have been eradicated
    private boolean[] eradicated;
    
    // array keeping track of how many disease cubes are left for each color
    private int[] dzCubesLeft;
    // list of cities with research stations
    private List<City> researchStations;
    
    // number of players
    private int numPlayers;
    // list of all the players
    private List<Player> players;
    
    // map from city name to city class itself
    private Map<String, City> cityMap;
    
    // difficulty level (4 - introductory, 5 - normal, 6 - heroic)
    private int difficulty; 
    
    private boolean winCondition = false;
    
    // if outbreaks == 8
    private boolean loseCondition1 = false;
    
    // if disease cubes are gone
    private boolean loseCondition2 = false;
    
    // if player cards run out
    private boolean loseCondition3 = false;
    
    /*
     * Main class that starts the game.  Basic format is to 
     */
    public Game(int numPlayers, int difficulty) {
        this.numPlayers = numPlayers;
        this.difficulty = difficulty;
    }

    public List<PlayerCard> getPlayerDeck() { return playerDeck; }
    public List<City> getInfectionDeck() { return infectionDeck; }
    public List<PlayerCard> getDiscardPlayerdeck() { return discardPlayerDeck; }
    public List<City> getDiscardInfectionDeck() { return discardInfectionDeck; }
    public int getOutbreakIndex() { return outbreakIndex; }
    public int getInfectionRateIndex() { return infectionRateIndex; }
    public boolean[] getCured() { return cured; }
    public int[] getDzCubesLeft() { return dzCubesLeft; }
    public List<City> getResearchStations() { return researchStations; }
    public int getNumPlayers() { return numPlayers; }
    public List<Player> getPlayers() { return players; }
    public Map<String, City> getCityMap() { return cityMap; }
    public boolean getWinCondition() { return winCondition; }
    public boolean getLoseCondition1() { return loseCondition1; }
    public boolean getLoseCondition2() { return loseCondition2; }
    public boolean getLoseCondition3() { return loseCondition3; }
    
    public boolean getGameOver() { 
        return winCondition || loseCondition1 || loseCondition2 || loseCondition3;
    }
    
    public void initializeGame() {
        try {
            cityMap = new HashMap<>();
            loadCities();
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }

        Set<String> cityNames = cityMap.keySet();
        Iterator iter = cityNames.iterator();
        playerDeck = new ArrayList<>();
        infectionDeck = new ArrayList<>();
        while (iter.hasNext()) {
            String name = (String)iter.next();
            playerDeck.add(new PlayerCard(name, false));
            infectionDeck.add(cityMap.get(name));
        }
        playerDeck.add(new PlayerCard("Government Grant", true));
        playerDeck.add(new PlayerCard("One Quiet Night", true));
        playerDeck.add(new PlayerCard("Resilient Population", true));
        playerDeck.add(new PlayerCard("Forecast", true));
        playerDeck.add(new PlayerCard("Airlift", true));
        Collections.shuffle(playerDeck);
        Collections.shuffle(infectionDeck);

        discardPlayerDeck = new ArrayList<>();
        discardInfectionDeck = new ArrayList<>();

        outbreakIndex = 0;
        infectionRateIndex = 0;

        cured = new boolean[4];
        dzCubesLeft = new int[4];
        eradicated = new boolean[4];
        for (int dzCounter = 0; dzCounter < 4; dzCounter++) {
            cured[dzCounter] = false;
            eradicated[dzCounter] = false;
            dzCubesLeft[dzCounter] = 24;
        }

        researchStations = new ArrayList<>();
        
        // Creating players;
        players = new ArrayList<>();
        List<String> roles = new ArrayList<>();
        roles.add("Dispatcher");
        roles.add("Medic");
        roles.add("Operations Expert");
        roles.add("Researcher");
        roles.add("Scientist");
        Collections.shuffle(roles);
        for (int pCounter = 0; pCounter < numPlayers; pCounter++) {
            String pRole = roles.get(pCounter);
            List<PlayerCard> pDeck = new ArrayList<>();
            for (int cards = 0; cards < 6 - numPlayers; cards++)
                pDeck.add(playerDeck.remove(0));
            players.add(new Player(cityMap.get("Atlanta"), pRole, pDeck));
        }
        
        // Inserting epidemic cards into player card deck
        switch (difficulty) {
            case 4: 
                for (int i = 0; i < 4; i++) {
                    int indexLoc = (int) Math.random() * 11 + 12 * i;
                    playerDeck.add(indexLoc, new PlayerCard("Epidemic", false));
                }
                break;
            case 5:
                for (int i = 0; i < 4; i++) {
                    int indexLoc = (int) Math.random() * 9 + 10 * i;
                    playerDeck.add(indexLoc, new PlayerCard("Epidemic", false));
                }
                for (int i2 = 4; i2 < 5; i2++) {
                    int indexLoc = (int) Math.random() * 8 + 36;
                    playerDeck.add(indexLoc, new PlayerCard("Epidemic", false));
                }
                break;
            case 6:
                for (int i = 0; i < 6; i++) {
                    int indexLoc = (int) Math.random() * 7 + 8 * i;
                    playerDeck.add(indexLoc, new PlayerCard("Epidemic", false));
                }
                break;
            default: 
                System.out.println("Difficulty value needs to be between 4 and 6");
                break;
        }
        
        // Place 1 research station in Atlanta
        City atlanta = cityMap.get("Atlanta");
        atlanta.setHasResearchStation(true);
        researchStations.add(atlanta);
        
        // Putting down initial disease cubes on the board
        for (int numCubes = 3; numCubes > 0; numCubes--) {
            for (int initCounter = 0; initCounter < 3; initCounter++) {
                City temp = infectionDeck.remove(0);
                discardInfectionDeck.add(temp);
                for (int cubes = 0; cubes < numCubes; cubes++) {
                    incrementDz(temp, temp.getColor());
                    dzCubesLeft[temp.getColor().index()]--;
                }
            }
        }
    }
    
    /* 
     * At the end of this method, city names are in this array
     */
    public void loadCities() throws IOException {
        BufferedReader f = new BufferedReader(new FileReader("Cities.csv"));
        Map <String, List<String>> tempConnect = new HashMap<>();

        while (f.ready()) {
            String input = f.readLine();
            String[] inputs = input.split(",");

            String name = inputs[0];
            DColor color;
            switch (inputs[1].toLowerCase()) {
                case "black": 
                    color = DColor.BLACK; break;
                case "yellow": 
                    color = DColor.YELLOW; break;
                case "red": 
                    color = DColor.RED; break;
                case "blue": 
                    color = DColor.BLUE; break;
                default: 
                    color = null;
                    System.out.println("City file has invalid color at " + name);
                    break;
            }

            List<String> list = new ArrayList<>();
            for (int i = 2; i < inputs.length; i++)
                list.add(inputs[i]);

            tempConnect.put(name, list);
            cityMap.put(name, new City(name, color));
        }
        
        Collection<City> tempList = cityMap.values();
        for (Iterator iter = tempList.iterator(); iter.hasNext(); ) {
            City tempCity = (City)iter.next();
            List<String> connections = tempConnect.get(tempCity.getName());
            for (int i = 0; i < connections.size(); i++) 
                tempCity.addConnectedCity(cityMap.get(connections.get(i)));
        }
    }
    
    public void playInfector() {
        int rate = 2;
        if (infectionRateIndex > 2)
            rate++;
        if (infectionRateIndex > 4)
            rate++;

        for (int i = 0; i < rate; i++) {
            City infectCity = infectionDeck.remove(0);
            discardInfectionDeck.add(infectCity);
            incrementDz(infectCity, infectCity.getColor());
        }
    }
    
    public void incrementDz(City city, DColor color) {
        if (eradicated[color.index()])
            return;
        if (city.getDzCubes(color) == 3) {
            outbreakIndex++;
            if (outbreakIndex == 8) {
                loseCondition1 = true;
                return;
            }                
            for (City next : city.getConnectedCities()) {
                if (loseCondition1 || loseCondition2 || loseCondition3)
                    return;
                incrementDz(next, color);
            }
        } else {
            if (dzCubesLeft[0] == 0 || dzCubesLeft[1] == 0 
                        || dzCubesLeft[2] == 0 || dzCubesLeft[3] == 0) {
                loseCondition2 = true;
                return;
            }
            city.addDzCube(color);
        }
    }
    
    public void drawCard(Player player) {
        PlayerCard pc = playerDeck.remove(0);
        player.getDeck().add(pc);
        if (playerDeck.isEmpty())
            loseCondition3 = true;
    }
    
    public void move(Player player, City loc, boolean dispatched) {
        // check if city is connected
        if (player.getCurrentCity().getConnectedCities().contains(loc))
            player.setCurrentCity(loc);

        // check if current city has a research station and new city has research station
        if (player.getCurrentCity().getHasResearchStation() && loc.getHasResearchStation())
            player.setCurrentCity(loc);

        // if dispatcher's moving, can move to another pawn
        if (dispatched) {
            // check if any other players are in that new city
            for (Player p : players) {
                if (p.getCurrentCity().equals(loc)) {
                    player.setCurrentCity(loc);
                    break;
                }
            }
        }
    }

    /*
        * Assumes that player has cards
        */
    public void moveToCard(Player player, PlayerCard pCard) {
        player.setCurrentCity(cityMap.get(pCard.getName()));
        player.getDeck().remove(pCard);
    }

    public void moveFromCard(Player player, PlayerCard pCard, City loc) {
        player.setCurrentCity(loc);
        player.getDeck().remove(pCard);
    }

    public void buildRS(Player player, City city) {
        if (!player.getRole().equalsIgnoreCase("Operations Expert")) {
            // if player is not operations expert, need playerCard to match
            for (PlayerCard pc : player.getDeck()) {
                if (pc.getName().equalsIgnoreCase(player.getCurrentCity().getName())) {
                    player.removeCard(pc);
                    cityMap.get(pc.getName()).setHasResearchStation(true);
                }
            }
        }

        if (city != null) {
            city.setHasResearchStation(false);
        }
    }
    
    public void cure(Player player, List<PlayerCard> pcs) {
        // check if player has the right number of cards (4 for medic, 5 everyone else)
        if (player.getRole().equalsIgnoreCase("medic") && pcs.size() == 4 
                || pcs.size() == 5) {
            // check if player has the right color cards
            DColor color = cityMap.get(pcs.remove(0).getName()).getColor();
            boolean matches = true;
            for (PlayerCard pc : pcs)
                if (cityMap.get(pc.getName()).getColor() != color) {
                    matches = false;
                    break;
                }

            if (matches)
                cured[color.index()] = true;
        }
    }
    
    public void treat(Player player, DColor color) {
        if (player.getRole().equalsIgnoreCase("medic") || cured[color.index()]) {
            int cubes = player.getCurrentCity().dzCubes()[color.index()];
            player.getCurrentCity().removeAllDzCubes(color);
            dzCubesLeft[color.index()] += cubes;
        } else {
            player.getCurrentCity().removeDzCube(color);
            dzCubesLeft[color.index()]++;
        }

        if (dzCubesLeft[color.index()] == 24 && cured[color.index()])
            eradicated[color.index()] = true;
    }
    
    public void share(Player player1, Player player2, PlayerCard playerCard) {
        if (player1.getCurrentCity() != player2.getCurrentCity())
            return;
        if (!player1.getRole().equalsIgnoreCase("researcher"))
            if (!player1.getCurrentCity().getName()
                    .equalsIgnoreCase(playerCard.getName()))
                return;
        // conditions are all met
        player1.removeCard(playerCard);
        player2.addCard(playerCard);
    }
    
    public void activatingEpidemics() {
        
    }

}